Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make RandomExample/RandomItemGenerator deterministic (use seed) #56

Merged
merged 10 commits into from
Jul 27, 2020
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 4.1.0

* Add `seed` parameter to `GovukSchemas::RandomExample` to make the random behaviour deterministic. Given the same seed, the same randomised outputs will be returned ([#56](https://github.com/alphagov/govuk_schemas/pull/56)).

# 4.0.1

* Bump the required Ruby version to >= 2.6.x.
Expand Down
2 changes: 0 additions & 2 deletions govuk_schemas.gemspec
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# coding: utf-8

lib = File.expand_path("lib", __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "govuk_schemas/version"
Expand Down
1 change: 0 additions & 1 deletion lib/govuk_schemas.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require "govuk_schemas/version"
require "govuk_schemas/schema"
require "govuk_schemas/utils"
require "govuk_schemas/random_example"
require "govuk_schemas/document_types"
require "govuk_schemas/example"
Expand Down
101 changes: 0 additions & 101 deletions lib/govuk_schemas/random.rb

This file was deleted.

126 changes: 126 additions & 0 deletions lib/govuk_schemas/random_content_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
module GovukSchemas
# @private
class RandomContentGenerator
WORDS = %w[Lorem ipsum dolor sit amet consectetur adipiscing elit. Ut suscipit at mauris non bibendum. Ut ac massa est. Aenean tempor imperdiet leo vel interdum. Nam sagittis cursus sem ultricies scelerisque. Quisque porttitor risus vel risus finibus eu sollicitudin nisl aliquet. Sed sed lectus ac dolor molestie interdum. Nam molestie pellentesque purus ac vestibulum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Suspendisse non tempor eros. Mauris eu orci hendrerit volutpat lorem in tristique libero. Duis a nibh nibh.].freeze

def initialize(random: Random.new)
@random = random
end

def string_for_type(type)
if type == "date-time"
time
elsif type == "uri"
uri
else
raise "Unknown attribute type `#{type}`"
end
end

def time
arbitrary_time = Time.new(2012, 2, 1)
(arbitrary_time + @random.rand(0..500_000_000)).iso8601
end

# TODO: make this more random with query string, optional anchor.
def uri
"http://example.com#{base_path}#{anchor}"
end

def base_path
"/" + @random.rand(1..5).times.map { uuid }.join("/")
end

def govuk_subdomain_url
subdomain = @random.rand(2..4).times.map {
("a".."z").to_a.sample(@random.rand(3..8), random: @random).join
}.join(".")
"https://#{subdomain}.gov.uk#{base_path}"
end

def string(minimum_chars = nil, maximum_chars = nil)
minimum_chars ||= 0
maximum_chars ||= 100
WORDS.sample(@random.rand(minimum_chars..maximum_chars), random: @random).join(" ")
end

def bool
@random.rand(2) == 1
end

def anchor
"##{hex}"
end

def random_identifier(separator:)
WORDS.sample(@random.rand(1..10), random: @random)
.join("-")
.gsub(/[^a-z0-9\-_]+/i, "-")
.gsub("-", separator)
end

def uuid
# matches uuid regex e.g. e058aad7-ce86-5181-8801-4ddcb3c8f27c
# /^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/
"#{hex(8)}-#{hex(4)}-1#{hex(3)}-a#{hex(3)}-#{hex(12)}"
end

def hex(length = 10)
length.times.map { bool ? random_letter : random_number }.join("")
end

def string_for_regex(pattern)
case pattern.to_s
when "^(placeholder|placeholder_.+)$"
["placeholder", "placeholder_#{WORDS.sample(random: @random)}"].sample(random: @random)
when "^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$"
uuid
when "^/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?$"
base_path
when "^[1-9][0-9]{3}[-/](0[1-9]|1[0-2])[-/](0[1-9]|[12][0-9]|3[0-1])$"
Date.today.iso8601
when "^[1-9][0-9]{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[0-1])$"
Date.today.iso8601
when "^#.+$"
anchor
when "[a-z-]"
random_identifier(separator: "-")
when "^[a-z_]+$"
random_identifier(separator: "_")
when "^/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?$"
base_path
when "^https://([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[A-Za-z0-9])?\\.)+campaign\\.gov\\.uk(/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?)?$"
govuk_subdomain_url
when "^https://([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[A-Za-z0-9])?\\.)*gov\\.uk(/(([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})+(/([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)*)?(\\?([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?(#([a-zA-Z0-9._~!$&'()*+,;=:@-]|%[0-9a-fA-F]{2})*)?)?$"
govuk_subdomain_url
when '[a-z0-9\-_]'
"#{hex}-#{hex}"
else
raise <<-DOC
Don't know how to generate random string for pattern #{pattern.inspect}

This propably means you've introduced a new regex in govuk-content-schemas.
Because it's very hard to generate a valid string from a regex alone,
we have to specify a method to generate random data for each regex in
the schemas.

To fix this:

- Add your regex to `lib/govuk_schemas/random.rb`
DOC
end
end

private

def random_letter
letters = ("a".."f").to_a
letters[@random.rand(0..letters.count - 1)]
end

def random_number
numbers = ("0".."9").to_a
numbers[@random.rand(0..numbers.count - 1)]
end
end
end
13 changes: 9 additions & 4 deletions lib/govuk_schemas/random_example.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require "govuk_schemas/random"
require "govuk_schemas/random_item_generator"
require "govuk_schemas/random_schema_generator"
require "json-schema"
require "json"

Expand All @@ -24,11 +23,17 @@ class RandomExample
# schema = GovukSchemas::Schema.find(frontend_schema: "detailed_guide")
# GovukSchemas::RandomExample.new(schema: schema).payload
#
# Example with seed (for consistent results):
#
# schema = GovukSchemas::Schema.find(frontend_schema: "detailed_guide")
# GovukSchemas::RandomExample.new(schema: schema, seed: 777).payload
# GovukSchemas::RandomExample.new(schema: schema, seed: 777).payload # returns same as above
#
# @param [Hash] schema A JSON schema.
# @return [GovukSchemas::RandomExample]
def initialize(schema:)
def initialize(schema:, seed: nil)
@schema = schema
@random_generator = RandomItemGenerator.new(schema: schema)
@random_generator = RandomSchemaGenerator.new(schema: schema, seed: seed)
end

# Returns a new `GovukSchemas::RandomExample` object.
Expand Down
Loading